using System;
using System.Collections.Generic;
using Drawing = System.Drawing;
using System.Text.RegularExpressions;

using Nairo.Parser.Dom;

namespace Nairo.Parser
{
	public static class CSSPropertyParser
	{
		static System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.InvariantCulture;
		
		public static BoxDistances ParseMarginOrPadding(string data)
		{
			string[] items = data.Split(' ');
			SizeValue left, right, top, bottom;
			
			switch (items.Length) {
				case 2:
					left = ParseSize(items[1]);
					right = ParseSize(items[1]);
					top = ParseSize(items[0]);
					bottom = ParseSize(items[0]);
					break;
				case 3:
					left = ParseSize(items[1]);
					right = ParseSize(items[1]);
					top = ParseSize(items[0]);
					bottom = ParseSize(items[2]);
					break;
				case 4:
					left = ParseSize(items[3]);
					right = ParseSize(items[1]);
					top = ParseSize(items[0]);
					bottom = ParseSize(items[2]);
					break;
				default:
					left = ParseSize(items[0]);
					right = ParseSize(items[0]);
					top = ParseSize(items[0]);
					bottom = ParseSize(items[0]);
					break;
			}
			return new BoxDistances(left, right, top, bottom);
		}
		
		public static FloatStyle ParseFloatStyle(string data)
		{
			Match match = Regex.Match(data, @"\s*(?<FloatStyle>((none|left|right|both|inherit)))\s*", RegexOptions.IgnoreCase);
			
			if (match != null) {
				Group style = match.Groups["FloatStyle"];
				if (style.Success) {
					return (FloatStyle)(FloatStyle.Parse(typeof(FloatStyle), style.Value[0].ToString().ToUpper() + style.Value.Substring(1).ToLower()));
				}
			}
			
			return FloatStyle.None;
		}
		
		public static DisplayStyle ParseDisplayStyle(string data)
		{
			Match match = Regex.Match(data, @"\s*(?<DisplayStyle>((none|block|inline-block|inline-table|inline|list-item|run-in|table-row-group|table-row|table-header-group|table-footer-group|table-colums-group|table-column|table-cell|table-caption|table)))\s*", RegexOptions.IgnoreCase);
			if (match != null) {
				Group style = match.Groups["DisplayStyle"];
				if (style.Success) {
					switch (style.Value.ToLower()) {
						case "none":
							return DisplayStyle.None;
						case "block":
							return DisplayStyle.Block;
						case "inline-block":
							return DisplayStyle.InlineBlock;
						case "listitem":
							return DisplayStyle.ListItem;
						case "run-in":
							return DisplayStyle.RunIn;
						case "inline-table":
							return DisplayStyle.InlineTable;
						case "table":
							return DisplayStyle.Table;
						case "table-caption":
							return DisplayStyle.TableCaption;
						case "table-cell":
							return DisplayStyle.TableCell;
						case "table-column":
							return DisplayStyle.TableColumn;
						case "table-columns-group":
							return DisplayStyle.TableColumnsGroup;
						case "table-footer-group":
							return DisplayStyle.TableFooterGroup;
						case "table-header-group":
							return DisplayStyle.TableHeaderGroup;
						case "table-row":
							return DisplayStyle.TableRow;
						case "table-row-group":
							return DisplayStyle.TableRowGroup;
					}
				}
			}
			
			return DisplayStyle.Inline;
		}
		
		public static BorderStyle[] ParseBorderStyle(string data)
		{
			string[] items = data.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
			BorderStyle[] styles = new BorderStyle[4];
			
			switch (items.Length) {
				case 2:
					styles[0] = ParseBorderStyleInternal(items[0]);
					styles[1] = ParseBorderStyleInternal(items[0]);
					styles[2] = ParseBorderStyleInternal(items[1]);
					styles[3] = ParseBorderStyleInternal(items[1]);
					break;
				case 3:
					styles[0] = ParseBorderStyleInternal(items[0]);
					styles[1] = ParseBorderStyleInternal(items[2]);
					styles[2] = ParseBorderStyleInternal(items[2]);
					styles[3] = ParseBorderStyleInternal(items[1]);
					break;
				case 4:
					styles[0] = ParseBorderStyleInternal(items[0]);
					styles[1] = ParseBorderStyleInternal(items[1]);
					styles[2] = ParseBorderStyleInternal(items[2]);
					styles[3] = ParseBorderStyleInternal(items[3]);
					break;
				default:
					styles[0] = ParseBorderStyleInternal(items[0]);
					styles[1] = ParseBorderStyleInternal(items[0]);
					styles[2] = ParseBorderStyleInternal(items[0]);
					styles[3] = ParseBorderStyleInternal(items[0]);
					break;
			}
			
			return styles;
		}
		
		private static BorderStyle ParseBorderStyleInternal(string data)
		{
			Match match = Regex.Match(data, @"\s*(?<BorderStyle>(none|dotted|dashed|solid|double|groove|ridge|inset|outset))\s*", RegexOptions.IgnoreCase);
			
			if (match != null) {
				Group borderStyle = match.Groups["BorderStyle"];
				if (borderStyle.Success)
					return (BorderStyle)(BorderStyle.Parse(typeof(BorderStyle), borderStyle.Value[0].ToString().ToUpper() + borderStyle.Value.Substring(1).ToLower()));
			}
			
			return BorderStyle.None;
		}
		
		public static SizeValue ParseSize(string data)
		{
			Match match = Regex.Match(data, @"^\s*(?<Size>([+-]?[0-9]*(\.[0-9]+)?\s*(?<Unit>(mm|cm|in|pt|pc|em|ex|px)?))|([0-9]*(\.[0-9]+)?(?<Unit>(\%))))\s*$");
			
			if (match != null) {
				string val = match.Groups["Size"].Value.ToLower();
				if (val.StartsWith("."))
					val = "0" + val;
				
				switch (match.Groups["Unit"].Value.ToLower()) {
					case "%":
						return new SizeValue(double.Parse(val.Replace("%", ""), culture), false, true, false, RelativeUnits.None, AbsoluteUnits.None, SpecialSizeValues.None);
					case "mm":
						return new SizeValue(double.Parse(val.Replace("mm", ""), culture), false, false, false, RelativeUnits.None, AbsoluteUnits.Millimeters, SpecialSizeValues.None);
					case "cm":
						return new SizeValue(double.Parse(val.Replace("cm", ""), culture), false, false, false, RelativeUnits.None, AbsoluteUnits.Centimeters, SpecialSizeValues.None);
					case "in":
						return new SizeValue(double.Parse(val.Replace("in", ""), culture), false, false, false, RelativeUnits.None, AbsoluteUnits.Inches, SpecialSizeValues.None);
					case "pt":
						return new SizeValue(double.Parse(val.Replace("pt", ""), culture), false, false, false, RelativeUnits.None, AbsoluteUnits.Points, SpecialSizeValues.None);
					case "pc":
						return new SizeValue(double.Parse(val.Replace("pc", ""), culture), false, false, false, RelativeUnits.None, AbsoluteUnits.Pico, SpecialSizeValues.None);
					case "em":
						return new SizeValue(double.Parse(val.Replace("em", ""), culture), true, false, false, RelativeUnits.EMSize, AbsoluteUnits.None, SpecialSizeValues.None);
					case "ex":
						return new SizeValue(double.Parse(val.Replace("ex", ""), culture), true, false, false, RelativeUnits.EXSize, AbsoluteUnits.None, SpecialSizeValues.None);
					case "px":
						return new SizeValue(double.Parse(val.Replace("px", ""), culture), true, false, false, RelativeUnits.Pixels, AbsoluteUnits.None, SpecialSizeValues.None);
					case "":
						return new SizeValue(0.0, false, false, false,
						                     RelativeUnits.None, AbsoluteUnits.Points, SpecialSizeValues.None);
					default:
						return new SizeValue(double.Parse(match.Groups["Size"].Value.ToLower()), false, false, false,
						                     RelativeUnits.None, AbsoluteUnits.None, SpecialSizeValues.None);
				}
			}
			
			return SizeValue.Empty;
		}
		
		public static string[] ParseFontFamily(string data)
		{
			Regex regex = new Regex("^((?<Names>([A-z]+|\\\"[A-z ]+\\\"|\\'[A-z ]+\\'|(sans-serif|serif|cursive|fantasy|monospace)))(?:,?\\s*))+$", RegexOptions.IgnoreCase);
			
			Match match = regex.Match(data);
			List<string> families = new List<string>();
			
			if (match != Match.Empty) {
				Group names = match.Groups["Names"];
				
				if (names != null) {
					foreach (Capture cap in names.Captures) {
						families.Add(cap.Value.Trim('\'', '"', ' ', ','));
					}
				}
			}
			
			return families.ToArray();
		}
		
		public static SizeValue ParseFontSize(string data)
		{
			data = data.Trim(' ', '\t');
			
			Match match = Regex.Match(data, @"^\s*(?<Length>(xx-large|x-large|large|medium|small|x-small|xx-small)|(smaller|larger)|([+-]?[0-9]*(\.[0-9]+)?\s*(?<Unit>(mm|cm|in|pt|pc|em|ex|px)?))|([0-9]*(\.[0-9]+)?(?<Unit>(\%))))\s*$");
	
			if (match != null) {
				switch (match.Groups["Length"].Value.ToLower()) {
					case "xx-large":
						return SizeValue.XXLarge;
					case "x-large":
						return SizeValue.XLarge;
					case "large":
						return SizeValue.Large;
					case "small":
						return SizeValue.Small;
					case "x-small":
						return SizeValue.XSmall;
					case "xx-small":
						return SizeValue.XXSmall;
					case "smaller":
						return SizeValue.Smaller;
					case "larger":
						return SizeValue.Larger;
				}
				
				return ParseSize(match.Groups["Length"].Value);
			}
			
			return SizeValue.Medium;
		}
		
		public static SizeValue ParseLineHeight(string data)
		{
			data = data.Trim(' ', '\t');
			
			Match match = Regex.Match(data, @"^\s*(?<Length>(normal|([+-]?[0-9]*(\.[0-9]+)?\s*(?<Unit>(mm|cm|in|pt|pc|em|ex|px)?))|([0-9]*(\.[0-9]+)?(?<Unit>(\%)))))\s*$");
			
			if (match != null) {
				switch (match.Groups["Unit"].Value.ToLower()) {
					case "%":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("%", "")), false, true, false, RelativeUnits.None, AbsoluteUnits.None, SpecialSizeValues.None);
					case "mm":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("mm", "")), false, false, false, RelativeUnits.None, AbsoluteUnits.Millimeters, SpecialSizeValues.None);
					case "cm":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("cm", "")), false, false, false, RelativeUnits.None, AbsoluteUnits.Centimeters, SpecialSizeValues.None);
					case "in":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("in", "")), false, false, false, RelativeUnits.None, AbsoluteUnits.Inches, SpecialSizeValues.None);
					case "pt":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("pt", "")), false, false, false, RelativeUnits.None, AbsoluteUnits.Points, SpecialSizeValues.None);
					case "pc":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("pc", "")), false, false, false, RelativeUnits.None, AbsoluteUnits.Pico, SpecialSizeValues.None);
					case "em":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("em", "")), true, false, false, RelativeUnits.EMSize, AbsoluteUnits.None, SpecialSizeValues.None);
					case "ex":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("ex", "")), true, false, false, RelativeUnits.EXSize, AbsoluteUnits.None, SpecialSizeValues.None);
					case "px":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower().Replace("px", "")), true, false, false, RelativeUnits.Pixels, AbsoluteUnits.None, SpecialSizeValues.None);
					case "":
						return new SizeValue(double.Parse(match.Groups["Length"].Value.ToLower()), false, false, false,
						                     RelativeUnits.None, AbsoluteUnits.None, SpecialSizeValues.None);
				}
			}
			
			return SizeValue.Normal;
		}
		
		public static FontStyle ParseFontStyle(string data)
		{
			Regex regex = new Regex(@"^\s*(normal|italic|oblique)\s*$", RegexOptions.IgnoreCase);
			
			Match match = regex.Match(data);
			
			if (match != Match.Empty) {
				Group g = match.Groups[0];
				
				if (g != null) {
					return (FontStyle)FontStyle.Parse(typeof(FontStyle), g.Value[0].ToString().ToUpper() + g.Value.Substring(1).ToLower());
				}
			}
			
			return FontStyle.Normal;
		}
		
		public static FontVariant ParseFontVariant(string data)
		{
			Regex regex = new Regex(@"^\s*(normal|small-caps)\s*$", RegexOptions.IgnoreCase);
			
			Match match = regex.Match(data);
			
			if (match != Match.Empty) {
				Group g = match.Groups[0];
				
				if (g != null) {
					switch (g.Value.ToLower()) {
						case "small-caps":
							return FontVariant.SmallCaps;
					}
				}
			}
			
			return FontVariant.Normal;
		}
		
		public static FontValue ParseFont(string data)
		{
			string fontSVW = @"((\s*(?<Style>(normal|italic|oblique)\s*))|(\s*(?<Variant>(normal|small-caps))\s*)|(\s*(?<Weight>(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900))\s*))?";
			string fontSizeData = @"\s*(?<FontSize>(xx-large|x-large|large|medium|small|x-small|xx-small)|(smaller|larger)|([+-]?[0-9]*(\.[0-9]+)?\s*(?<FontSizeUnit>(mm|cm|in|pt|pc|em|ex|px)?))|([0-9]*(\.[0-9]+)?(?<FontSizeUnit>(\%))))\s*";
			string lineHeightData = @"\s*(\s*(\s*(?<LineHeight>(normal|([+-]?[0-9]*(\.[0-9]+)?\s*(?<LineHeightUnit>(mm|cm|in|pt|pc|em|ex|px)?))|([0-9]*(\.[0-9]+)?(?<LineHeightUnit>(\%)))))\s*)?\s*";
			string fontFamily = "\\s*((?<Names>([A-z]+|\\\"[A-z ]+\\\"|\\'[A-z ]+\\'|(sans-serif|serif|cursive|fantasy|monospace)))(?:,?\\s*))+\\s*";
			string regexPattern = "^(" + fontSVW + fontSVW + fontSVW + ")?(" + fontSizeData + ")(" + lineHeightData + ")(" + fontFamily + "))$";
			
			Match match = Regex.Match(data, regexPattern);
			string[] families = new string[0];
			FontStyle fontStyle = FontStyle.Normal;
			FontWeight fontWeight = FontWeight.Normal;
			FontVariant fontVariant = FontVariant.Normal;
			SizeValue fontSize = SizeValue.Medium;
			SizeValue lineHeight = SizeValue.Normal;
			
			if (match != null) {
				Group names = match.Groups["Names"];
				Group style = match.Groups["Style"];
				Group variant = match.Groups["Variant"];
				Group weight = match.Groups["Weight"];
				Group size = match.Groups["FontSize"];
				Group lHeight = match.Groups["LineHeight"];
				
				if (names.Success)
					families = ParseFontFamily(names.Value);
				
				if (style.Success)
					fontStyle = ParseFontStyle(style.Value);
				
				if (weight.Success)
					fontWeight = ParseFontWeight(weight.Value);
				
				if (variant.Success)
					fontVariant = ParseFontVariant(variant.Value);
				
				if (lHeight.Success)
					lineHeight = ParseLineHeight(lHeight.Value);
				
				if (size.Success)
					fontSize = ParseFontSize(size.Value);
			}
			
			return new FontValue(SpecialValues.None, families, fontWeight, fontSize, fontVariant, fontStyle, lineHeight);
		}
		
		public static Border ParseBorder(string data)
		{
			string width = @"\s*(?<BorderSize>((thin|medium|thick)|([+-]?[0-9]*(\.[0-9]+)?\s*(?<SizeUnit>(mm|cm|in|pt|pc|em|ex|px)?))|([0-9]*(\.[0-9]+)?(?<SizeUnit>(\%)))))\s*";
			string colorData = @"(?<BorderColor>((#[0-9ABCDEF]{3,6})|(transparent|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)|(rgb\s*\((\s*[0-9]{1,3}%?,\s*){2}\s*[0-9]{1,3}%?\s*\)\s*)))";
			string borderData = @"\s*(" + colorData + ")|(?<BorderStyle>(none|dotted|dashed|solid|double|groove|ridge|inset|outset))|(" + width + ")\\s*";
			Match match = Regex.Match(data, "^\\s*(" + borderData + "){1,3}\\s*$", RegexOptions.IgnoreCase);
			
			Drawing.Color color = Drawing.Color.Black;
			BorderStyle style = BorderStyle.None;
			SizeValue borderWidth = SizeValue.Medium;
			
			if (match != null) {
				Group size = match.Groups["BorderSize"];
				Group borderColor = match.Groups["BorderColor"];
				Group borderStyle = match.Groups["BorderStyle"];
				
				if (borderColor.Success) {
					color = ConversionHelper.ConvertToColor(borderColor.Value);
				}
				
				if (borderStyle.Success) {
					style = ParseBorderStyleInternal(borderStyle.Value);
				}
				
				if (size.Success) {
					borderWidth = ParseSize(size.Value);
				}
	
			}
			
			return new Border(color, borderWidth, style);
		}
		
		public static FontWeight ParseFontWeight(string data)
		{
			Regex regex = new Regex(@"^\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)\s*$", RegexOptions.IgnoreCase);
			
			Match match = regex.Match(data);
			
			if (match != Match.Empty) {
				Group g = match.Groups[0];
				
				if (g != null) {
					switch (g.Value.ToLower()) {
						case "bold":
							return FontWeight.Bold;
						case "bolder":
							return FontWeight.Bolder;
						case "lighter":
							return FontWeight.Lighter;
						case "100":
							return FontWeight.Value100;
						case "200":
							return FontWeight.Value200;
						case "300":
							return FontWeight.Value300;
						case "400":
							return FontWeight.Value400;
						case "500":
							return FontWeight.Value500;
						case "600":
							return FontWeight.Value600;
						case "700":
							return FontWeight.Value700;
						case "800":
							return FontWeight.Value800;
						case "900":
							return FontWeight.Value900;
					}
				}
			}
			
			return FontWeight.Normal;
		}
	}
}
